package com.gitee.wsl.doc.prosemirror.model.node.inline

import com.gitee.wsl.doc.prosemirror.exception.RangeError
import com.gitee.wsl.doc.prosemirror.model.attr.Attrs
import com.gitee.wsl.doc.prosemirror.model.mark.Mark
import com.gitee.wsl.doc.prosemirror.model.node.InlineNode
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.model.node.NodeType
import com.gitee.wsl.doc.prosemirror.model.node.wrapMarks
import com.gitee.wsl.doc.prosemirror.util.slice
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive

class TextNode : InlineNode {
    override val text: String

    override val textContent: String
        get() = this.text

    override val nodeSize: Int
        get() = this.text.length

    constructor(type: NodeType, attrs: Attrs, content: String, marks: List<Mark>?) :
        super(type, attrs, marks ?: Mark.none) {
        if (content.isEmpty()) throw RangeError("Empty text nodes are not allowed")
        this.text = content
    }

    override fun toString(): String {
        if (this.type.spec.toDebugString != null) return this.type.spec.toDebugString!!(this)
        return wrapMarks(this.marks, Json.encodeToString(this.text))
    }

    fun textBetween(from: Int, to: Int): String {
        return this.text.slice(from, to)
    }

    override fun mark(marks: List<Mark>): Node {
        return if (marks == this.marks) {
            this
        } else {
            TextNode(this.type, this.attrs, this.text, marks)
        }
    }

    fun withText(text: String): TextNode {
        if (text == this.text) return this
        return TextNode(this.type, this.attrs, text, this.marks)
    }

    override fun cut(from: Int, to: Int?): Node {
        val end = to ?: this.text.length
        if (from == 0 && end == this.text.length) return this
        return this.withText(this.text.slice(from, end))
    }

    override fun toJSON(): JsonObject {
        val base = super.toJSON()
        return JsonObject(
            base.toMutableMap().apply {
                put("text", JsonPrimitive(text))
            }
        )
    }

    override fun toJSONWithId(): JsonObject {
        val base = super.toJSONWithId()
        return JsonObject(
            base.toMutableMap().apply {
                put("text", JsonPrimitive(text))
            }
        )
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is TextNode) return false
        if (!super.equals(other)) return false

        return this.sameMarkup(other) && this.text == other.text
    }

    override fun hashCode(): Int {
        return text.hashCode()
    }
}